home *** CD-ROM | disk | FTP | other *** search
- /* tail - copy the end of a file Author: Norbert Schlenker */
-
- /* Syntax: tail [-f] [-c number | -n number] [file]
- * tail -[number][c|l][f] [file] (obsolescent)
- * tail +[number][c|l][f] [file] (obsolescent)
- * Flags:
- * -c number Measure starting point in bytes. If number begins
- * with '+', the starting point is relative to the
- * the file's beginning. If number begins with '-'
- * or has no sign, the starting point is relative to
- * the end of the file.
- * -f Keep trying to read after EOF on files and FIFOs.
- * -n number Measure starting point in lines. The number
- * following the flag has significance similar to
- * that described for the -c flag.
- *
- * If neither -c nor -n are specified, the default is tail -n 10.
- *
- * In the obsolescent syntax, an argument with a 'c' following the
- * (optional) number is equivalent to "-c number" in the standard
- * syntax, with number including the leading sign ('+' or '-') of the
- * argument. An argument with 'l' following the number is equivalent
- * to "-n number" in the standard syntax. If the number is not
- * specified, 10 is used as the default. If neither 'c' nor 'l' are
- * specified, 'l' is assumed. The character 'f' may be suffixed to
- * the argument and is equivalent to specifying "-f" in the standard
- * syntax. Look for lines marked "OBSOLESCENT".
- *
- * If no file is specified, standard input is assumed.
- *
- * P1003.2 does not specify tail's behavior when a count of 0 is given.
- * It also does not specify clearly whether the first byte (line) of a
- * file should be numbered 0 or 1. Historical behavior is that the
- * first byte is actually number 1 (contrary to all Unix standards).
- * Historically, a count of 0 (or -0) results in no output whatsoever,
- * while a count of +0 results in the entire file being copied (just like
- * +1). The implementor does not agree with these behaviors, but has
- * copied them slavishly. Look for lines marked "HISTORICAL".
- *
- * Author: Norbert Schlenker
- * Copyright: None. Released to the public domain.
- * Reference: P1003.2 section 4.59 (draft 10)
- * Notes: Under Minix, this program requires chmem =30000.
- * Bugs: No internationalization support; all messages are in English.
- */
-
- /* Force visible Posix names */
- #ifndef _POSIX_SOURCE
- #define _POSIX_SOURCE 1
- #endif
-
- /* External interfaces */
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <unistd.h>
- #include <ctype.h>
- #include <stdlib.h>
- #include <stdio.h>
- #include <dos/dos.h>
- #include <clib/dos_protos.h>
-
- #include "getopt.h"
-
- /* External interfaces that should have been standardized into <getopt.h> */
- #ifdef _MINIX
- int getopt (int argc, char **argv, char *options);
- #else
- extern int getopt();
- #endif
- extern char *optarg;
- extern int optind;
-
- /* We expect this constant to be defined in <limits.h> in a Posix program,
- * but we'll specify it here just in case it's been left out.
- */
- #ifndef LINE_MAX
- #define LINE_MAX 2048 /* minimum acceptable lower bound */
- #endif
-
- /* Magic numbers suggested or required by Posix specification */
- #define SUCCESS 0 /* exit code in case of success */
- #define FAILURE 1 /* or failure */
- #define DEFAULT_COUNT 10 /* default number of lines or bytes */
- #define MIN_BUFSIZE (LINE_MAX * DEFAULT_COUNT)
- #define SLEEP_INTERVAL 1 /* sleep for one second intervals with -f */
-
- #define sleep(t) Delay(t*50) /* amigaDOS function */
-
- #define FALSE 0
- #define TRUE 1
-
- /* Internal functions - prototyped under Minix */
- int main (int argc, char **argv);
- int tail (int count, int bytes, int read_until_killed);
- int keep_reading (void);
- void usage (void);
-
- int main(argc, argv)
- /* [<][>][^][v][top][bottom][index][help] */
- int argc;
- char *argv[];
- {
- int cflag = FALSE;
- int nflag = FALSE;
- int fflag = FALSE;
- int number = -DEFAULT_COUNT;
- char *suffix;
- int opt;
- struct stat stat_buf;
-
- /* Determining whether this invocation is via the standard syntax or
- * via an obsolescent one is a nasty kludge. Here it is, but there is
- * no pretense at elegance.
- */
- if (argc == 1) { /* simple: default read of a pipe */
- exit(tail(-DEFAULT_COUNT, 0, fflag));
- }
- if ((argv[1][0] == '+') || /* OBSOLESCENT */
- (argv[1][0] == '-' && ((isdigit(argv[1][1])) ||
- (argv[1][1] == 'l') ||
- (argv[1][1] == 'c' && argv[1][2] == 'f')))) {
- --argc; ++argv;
- if (isdigit(argv[0][1])) {
- number = (int)strtol(argv[0], &suffix, 10);
- if (number == 0) { /* HISTORICAL */
- if (argv[0][0] == '+')
- number = 1;
- else
- exit(SUCCESS);
- }
- } else {
- number = (argv[0][0] == '+') ? DEFAULT_COUNT : -DEFAULT_COUNT;
- suffix = &(argv[0][1]);
- }
- if (*suffix != '\0') {
- if (*suffix == 'c') {
- cflag = TRUE;
- ++suffix;
- }
- else
- if (*suffix == 'l') {
- nflag = TRUE;
- ++suffix;
- }
- }
- if (*suffix != '\0') {
- if (*suffix == 'f') {
- fflag = TRUE;
- ++suffix;
- }
- }
- if (*suffix != '\0') { /* bad form: assume to be a file name */
- number = -DEFAULT_COUNT;
- cflag = nflag = FALSE;
- fflag = FALSE;
- } else {
- --argc; ++argv;
- }
- } else { /* new standard syntax */
- while ((opt = getopt(argc, argv, "c:fn:")) != EOF) {
- switch (opt) {
- case 'c':
- cflag = TRUE;
- if (*optarg == '+' || *optarg == '-')
- number = atoi(optarg);
- else
- if (isdigit(*optarg))
- number = -atoi(optarg);
- else
- usage();
- if (number == 0) { /* HISTORICAL */
- if (*optarg == '+')
- number = 1;
- else
- exit(SUCCESS);
- }
- break;
- case 'f':
- fflag = TRUE;
- break;
- case 'n':
- nflag = TRUE;
- if (*optarg == '+' || *optarg == '-')
- number = atoi(optarg);
- else
- if (isdigit(*optarg))
- number = -atoi(optarg);
- else
- usage();
- if (number == 0) { /* HISTORICAL */
- if (*optarg == '+')
- number = 1;
- else
- exit(SUCCESS);
- }
- break;
- default:
- usage();
- /* NOTREACHED */
- }
- }
- argc -= optind;
- argv += optind;
- }
-
- if (argc > 1 || /* too many arguments */
- (cflag && nflag)) { /* both bytes and lines specified */
- usage();
- }
-
- if (argc > 0) { /* an actual file */
- if (freopen(argv[0], "r", stdin) != stdin) {
- fputs("tail: could not open ", stderr);
- fputs(argv[0], stderr);
- fputs("\n", stderr);
- exit(FAILURE);
- }
- /* There is an optimization possibility here. If a file is being
- * read, we need not look at the front of it. If we seek backwards
- * from the end, we can (potentially) avoid looking at most of the
- * file. Some systems fail when asked to seek backwards to a point
- * before the start of the file, so we avoid that possibility.
- */
- if (number < 0 && fstat(fileno(stdin), &stat_buf) == 0) {
- long offset = cflag ? (long)number : (long)number * LINE_MAX;
-
- if (-offset < stat_buf.st_size)
- fseek(stdin, offset, SEEK_END);
- }
- } else {
- fflag = FALSE; /* force -f off when reading a pipe */
- }
- exit(tail(number, cflag, fflag));
- /* NOTREACHED */
- }
-
- int tail(count, bytes, read_until_killed)
- /* [<][>][^][v][top][bottom][index][help] */
- int count; /* lines or bytes desired */
- int bytes; /* TRUE if we want bytes */
- int read_until_killed; /* keep reading at EOF */
- {
- int c;
- char *buf; /* pointer to input buffer */
- char *buf_end; /* and one past its end */
- char *start; /* pointer to first desired character in buf */
- char *finish; /* pointer past last desired character */
- int wrapped_once = FALSE; /* TRUE after buf has been filled once */
-
- /* This is magic. If count is positive, it means start at the count'th
- * line or byte, with the first line or byte considered number 1. Thus,
- * we want to SKIP one less line or byte than the number specified. In
- * the negative case, we look backward from the end of the file for the
- * (count + 1)'th newline or byte, so we really want the count to be one
- * LARGER than was specified (in absolute value). In either case, the
- * right thing to do is:
- */
- --count;
-
- /* Count is positive: skip the desired lines or bytes and then copy. */
- if (count >= 0) {
- while (count > 0 && (c = getchar()) != EOF) {
- if (bytes || c == '\n')
- --count;
- }
- while ((c = getchar()) != EOF) {
- if (putchar(c) == EOF)
- return FAILURE;
- }
- if (read_until_killed)
- return keep_reading();
- return ferror(stdin) ? FAILURE : SUCCESS;
- }
-
- /* Count is negative: allocate a reasonably large buffer. */
- if ((buf = (char *)malloc(MIN_BUFSIZE + 1)) == (char *)NULL) {
- fputs("tail: out of memory\n", stderr);
- return FAILURE;
- }
- buf_end = buf + (MIN_BUFSIZE + 1);
-
- /* Read the entire file into the buffer. */
- finish = buf;
- while ((c = getchar()) != EOF) {
- *finish++ = c;
- if (finish == buf_end) {
- finish = buf;
- wrapped_once = TRUE;
- }
- }
- if (ferror(stdin))
- return FAILURE;
-
- /* Back up inside the buffer. The count has already been adjusted to
- * back up exactly one character too far, so we will bump the buffer
- * pointer once after we're done.
- *
- * BUG: For large line counts, the buffer may not be large enough to
- * hold all the lines. The specification allows the program to
- * fail in such a case - this program will simply dump the entire
- * buffer's contents as its best attempt at the desired behavior.
- */
- if (finish != buf || wrapped_once) { /* file was not empty */
- start = (finish == buf) ? buf_end - 1 : finish - 1;
- while (start != finish) {
- if ((bytes || *start == '\n') && ++count == 0)
- break;
- if (start == buf) {
- start = buf_end - 1;
- if (!wrapped_once) /* never wrapped: stop now */
- break;
- } else {
- --start;
- }
- }
- if (++start == buf_end) { /* bump after going too far */
- start = buf;
- }
- if (finish > start) {
- fwrite(start, 1, finish - start, stdout);
- } else {
- fwrite(start, 1, buf_end - start, stdout);
- fwrite(buf, 1, finish - buf, stdout);
- }
- }
- if (read_until_killed)
- return keep_reading();
- return ferror(stdout) ? FAILURE : SUCCESS;
- }
-
- /* Wake at intervals to reread standard input. Copy anything read to
- * standard output and then go to sleep again.
- */
- int keep_reading()
- /* [<][>][^][v][top][bottom][index][help] */
- {
- int c;
-
- for (;;) {
- sleep(SLEEP_INTERVAL);
- clearerr(stdin);
- while ((c = getchar()) != EOF) {
- if (putchar(c) == EOF)
- return FAILURE;
- }
- if (ferror(stdin))
- return FAILURE;
- }
- }
-
- /* Tell the user the standard syntax. */
- void usage()
- /* [<][>][^][v][top][bottom][index][help] */
- {
- fputs("Usage: tail [-f] [-c number | -n number] [file]\n", stderr);
- exit(FAILURE);
- }
- /* [<][>][^][v][top][bottom][index][help] */
-